#include <stdio.h>
#include <string.h>

#include <windows.h>
#include <gdiplus.h>
#include <mlang.h>

#include "json/json.h"

#include "win32.h"

static wchar_t *string_to_bstr(const char *str) {
	size_t size = (MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0) + 1) * sizeof(wchar_t);
	wchar_t *tmp = malloc(size);
	wchar_t *bstr;

	memset(tmp, 0, size);
	MultiByteToWideChar(CP_ACP, 0, str, strlen(str), tmp, size);
	bstr = SysAllocString(tmp);
	free(tmp);
	return bstr;
}

static char *bstr_to_string(const wchar_t *bstr) {
	size_t size = WideCharToMultiByte(CP_ACP, 0, bstr, -1, NULL, 0, NULL, NULL );
	char *str = malloc(size + 1);
	memset(str, 0, size + 1);
	WideCharToMultiByte(CP_ACP, 0, bstr, -1, str, size, NULL, NULL );
	return str;
}

/*
 * unfortunately IID_IMultiLanguage2 is not included in any libXXX.a
 * in Cygwin(mingw32).
 */
#if defined(__CYGWIN__) || defined(__MINGW32__)
#undef IID_IMultiLanguage2
const IID IID_IMultiLanguage2 = { 0xDCCFC164, 0x2B38, 0x11d2, { 0xB7, 0xEC, 0x00, 0xC0, 0x4F, 0x8F, 0x5D, 0x9A } };
#endif

static HRESULT invoke(WORD autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs, ...) {
	/* Variables used */
	DISPPARAMS dp = { NULL, NULL, 0, 0 };
	HRESULT hr = 0;
	DISPID dispidNamed = DISPID_PROPERTYPUT;
	DISPID dispID;
	char *szName = NULL;
	int i;
	/* Allocate memory for arguments */
	VARIANT *pArgs = NULL;

	/* Begin variable-argument list */
	va_list marker = NULL;
	va_start(marker, cArgs);

	pArgs = malloc(sizeof(VARIANT) * (cArgs + 1));
	/* Extract arguments */
	for (i = 0; i < cArgs; i++) {
		pArgs[i] = va_arg(marker, VARIANT);
	}
	// Build DISPPARAMS
	dp.cArgs = cArgs;
	dp.rgvarg = pArgs;

	/* Handle special-case for property-puts! */
	if (autoType & DISPATCH_PROPERTYPUT) {
		dp.cNamedArgs = 1;
		dp.rgdispidNamedArgs = &dispidNamed;
	}

	if (!pDisp) {
		printf("NULL IDispatch passed to Invoke()");
		goto end;
	}

	/* Convert down to ANSI */
	szName = bstr_to_string(ptName);

	/* Get DISPID for name passed */
	hr = pDisp->lpVtbl->GetIDsOfNames(pDisp, &IID_NULL, &ptName, 1, LOCALE_SYSTEM_DEFAULT, &dispID);
	if (FAILED(hr)) {
		printf("IDispatch::GetIDsOfNames(%s) failed 0x%8x", szName, (unsigned int) hr);
		goto end;
	}

	/* Make the call! */
	hr = pDisp->lpVtbl->Invoke(pDisp, dispID, &IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL );
	if (FAILED(hr)) {
		printf("IDispatch::Invoke(%s=0x%x) failed 0x%x", szName, (unsigned int) dispID, (unsigned int) hr);
		goto end;
	}

	end:
	/* End variable-argument section */
	va_end(marker);
	free(pArgs);
	free(szName);

	return hr;
}

static HRESULT resolve_variant(VARIANT *pVarIn, VARIANT *pVarOut) {
	VARIANT *tmpVarIn = pVarIn;
	ULONG ByRef = 0;

	while (tmpVarIn->vt == (VT_BYREF | VT_VARIANT))
		tmpVarIn = tmpVarIn->pvarVal;

	if (tmpVarIn->vt & VT_BYREF)
		ByRef = VT_BYREF;

	switch (tmpVarIn->vt) {
	case VT_I2:
	case VT_BYREF | VT_I2:
		if (ByRef) {
			if (!tmpVarIn->piVal)
				return E_POINTER;
			pVarOut->iVal = *(tmpVarIn->piVal);
		}
		break;

	case VT_I4:
	case VT_BYREF | VT_I4:
		if (ByRef) {
			if (!tmpVarIn->plVal)
				return E_POINTER;
			pVarOut->lVal = *(tmpVarIn->plVal);
		}
		break;

	case VT_BSTR:
	case VT_BYREF | VT_BSTR:
		if (ByRef) {
			if (!tmpVarIn->pbstrVal)
				return E_POINTER;
			if (!*(tmpVarIn->pbstrVal))
				return E_POINTER;
			pVarOut->bstrVal = *(tmpVarIn->pbstrVal);
		}
		break;

	case VT_DISPATCH:
	case VT_BYREF | VT_DISPATCH:
		if (ByRef) {
			if (!tmpVarIn->ppdispVal)
				return E_POINTER;
			if (!*(tmpVarIn->ppdispVal))
				return E_POINTER;
			pVarOut->pdispVal = *(tmpVarIn->ppdispVal);
		}
		break;

	default:
		return DISP_E_TYPEMISMATCH;
	}

	if (ByRef)
		pVarOut->vt = tmpVarIn->vt - (VARTYPE) ByRef;
	else
		*pVarOut = *tmpVarIn;

	if (pVarOut->vt == VT_DISPATCH) {
		VARIANT varResolved;
		DISPPARAMS dispParamsNoArgs = { NULL, NULL, 0, 0 };
		VariantInit(&varResolved);
		if (SUCCEEDED(
				pVarOut->pdispVal->lpVtbl->Invoke(pVarOut->pdispVal, DISPID_VALUE, &IID_NULL, LOCALE_SYSTEM_DEFAULT,
						DISPATCH_PROPERTYGET | DISPATCH_METHOD, &dispParamsNoArgs, &varResolved, NULL, NULL ))) {
			resolve_variant(&varResolved, pVarOut);
		} else {
			return E_FAIL;
		}
		VariantClear(&varResolved);
		return S_OK;
	} else {
		HRESULT hr;
		VARIANT retVar;
		VariantInit(&retVar);
		hr = VariantCopy(&retVar, pVarOut);
		*pVarOut = retVar;
		return hr;
	}
}

static GString *variant_value(VARIANT *var) {
	GString *ret = NULL;
	switch (var->vt) {
	case VT_BSTR: {
		char *str = bstr_to_string(var->bstrVal);
		ret = g_string_new(str);
		free(str);
	}
		break;

	case VT_DISPATCH: {
		VARIANT varOut;
		VariantInit(&varOut);
		if (SUCCEEDED(resolve_variant(var, &varOut))) {
			ret = variant_value(&varOut);
			VariantClear(&varOut);
		}
	}
		break;

	case VT_R4: {
		char str[256];
		memset(str, 0, sizeof(str));
		sprintf(str, "%f", var->fltVal);
		ret = g_string_new(str);
	}
		break;

	case VT_R8: {
		char str[256];
		memset(str, 0, sizeof(str));
		sprintf(str, "%lf", var->dblVal);
		ret = g_string_new(str);
	}
		break;

	case VT_I4:
	case VT_INT: {
		char str[64];
		memset(str, 0, sizeof(str));
		sprintf(str, "%d", var->intVal);
		ret = g_string_new(str);
	}
		break;
	}

	return ret;
}

GString *script(const char *language, const char *code, const char *expr) {
	GString *ret = NULL;
	HRESULT hr;
	VARIANT result;
	CLSID clsid;
	IDispatch *pScriptControl;
	VARIANT arg;

	CLSIDFromProgID(L"MSScriptControl.ScriptControl", &clsid);
	CoInitialize(NULL );
	CoCreateInstance(&clsid, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, &IID_IDispatch,
			(void **) &pScriptControl);

	VariantClear(&result);

	VariantClear(&arg);
	arg.vt = VT_BOOL;
	arg.boolVal = TRUE;
	invoke(DISPATCH_PROPERTYPUT, NULL, pScriptControl, (LPOLESTR) L"AllowUI", 1, arg);
	VariantClear(&arg);

	arg.vt = VT_BOOL;
	arg.boolVal = FALSE;
	invoke(DISPATCH_PROPERTYPUT, NULL, pScriptControl, (LPOLESTR) L"UseSafeSubset", 1, arg);
	VariantClear(&arg);

	arg.vt = VT_I4;
	arg.lVal = -1;
	invoke(DISPATCH_PROPERTYPUT, NULL, pScriptControl, (LPOLESTR) L"Timeout", 1, arg);
	VariantClear(&arg);

	arg.vt = VT_BSTR;
	arg.bstrVal = string_to_bstr(language);
	hr = invoke(DISPATCH_PROPERTYPUT, NULL, pScriptControl, (LPOLESTR) L"Language", 1, arg);
	VariantClear(&arg);

	VariantInit(&arg);
	arg.vt = VT_BSTR;
	arg.bstrVal = string_to_bstr(code);
	if (FAILED(invoke(DISPATCH_METHOD, NULL, pScriptControl, (LPOLESTR) L"AddCode", 1, arg))) {
		printf("Invoke AddCode error.");
	}
	VariantClear(&arg);

	arg.vt = VT_BSTR;

	char *_expr = g_convert(expr, -1, "GBK", "UTF-8", NULL, NULL, NULL );
	arg.bstrVal = string_to_bstr(_expr);
	g_free(_expr);

	if (FAILED(invoke(DISPATCH_METHOD, &result, pScriptControl, (LPOLESTR) L"Eval", 1, arg))) {
		printf("Invoke Eval error.");
	}
	VariantClear(&arg);

	ret = variant_value(&result);

	VariantClear(&result);
	pScriptControl->lpVtbl->Release(pScriptControl);
	CoUninitialize();

	return ret;
}

char *codepage(const char *str) {
	char *retval = NULL;
	IMultiLanguage2 *mlang = NULL;
	DetectEncodingInfo info = { 0 };
	MIMECPINFO codepageinfo;
	int length = strlen(str), cnt = 1;
	HRESULT hr;

	CoInitialize(NULL );
	hr = CoCreateInstance(&CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, &IID_IMultiLanguage2, (void **) &mlang);
	hr = mlang->lpVtbl->DetectInputCodepage(mlang, MLDETECTCP_NONE, 0, (char *) str, &length, &info, &cnt);
	if (SUCCEEDED(hr)) {
#if defined(__CYGWIN__) ||  defined(__MINGW32__)
		hr = mlang->lpVtbl->GetCodePageInfo(mlang, info.nCodePage, &codepageinfo);
#else
		hr = mlang->lpVtbl->GetCodePageInfo(mlang, info.nCodePage, (LANGID)info.nLangID, &codepageinfo);
#endif
		if (SUCCEEDED(hr)) {
			retval = bstr_to_string(codepageinfo.wszBodyCharset);
		}
	}
	mlang->lpVtbl->Release(mlang);
	CoUninitialize();

	return retval;
}

GString *dyncall(const char *json) {
	union real {
		double real;
		struct {
			int head, tail;
		} val;
	} _real;
	int retval = 0;
	unsigned long register _esp = 0;
	GString *result = g_string_new("");
	unsigned int func;
	HMODULE hModule = NULL;

	//"{'file':'c:/windows/system32/user32.dll','method':'MessageBoxA','returntype':'int','params':[0,'test','test',1]}"
	struct json_object *obj = json_tokener_parse(json);
	const char *file = json_object_get_string(json_object_object_get(obj, "file"));
	const char *function = json_object_get_string(json_object_object_get(obj, "method"));
	const char *returntype = json_object_get_string(json_object_object_get(obj, "returntype"));
	struct json_object *args = json_object_object_get(obj, "params");
	int length = json_object_array_length(args);
	int self = !file || strlen(file) == 0;
	if (self) {
		hModule = GetModuleHandle(NULL );
	} else {
		hModule = LoadLibraryA(file);
	}

	func = (unsigned int) GetProcAddress(hModule, function);
	if (!func) {
		printf("Call GetProcAddress failed.");
	}

#ifdef _MSC_VER
	__asm mov int ptr[_esp], esp;
#else
	__asm__ __volatile__("movl %%esp, %0" : "=r"(_esp));
#endif
	if (length > 0) {
		int i;
		for (i = length - 1; i >= 0; i--) {
			struct json_object *arg = json_object_array_get_idx(args, i);

			switch (json_object_get_type(arg)) {
			case json_type_null: {
#ifdef _MSC_VER
				__asm push 0;
#else
				__asm__ __volatile__("pushl $0");
#endif

			}
				break;

			case json_type_boolean: {
				int b = json_object_get_boolean(arg);
#ifdef _MSC_VER
				__asm push b;
#else
				__asm__ __volatile__("pushl %0" : : "g"(b));
#endif
			}
				break;

			case json_type_double: {
				_real.real = json_object_get_double(arg);

#ifdef _MSC_VER
				__asm push _real.tail;
				__asm push _real.head;
#else
				__asm__ __volatile__("pushl %0" : : "g"(_real.val.tail));
				__asm__ __volatile__("pushl %0" : : "g"(_real.val.head));
#endif
			}
				break;

			case json_type_int: {
				int n = json_object_get_int(arg);
#ifdef _MSC_VER
				__asm push n;
#else
				__asm__ __volatile__("pushl %0" : : "g"(n));
#endif

			}
				break;

			case json_type_string: {
				const char *s = json_object_get_string(arg);
#ifdef _MSC_VER
				__asm push s;
#else
				__asm__ __volatile__("pushl %0" : : "g"(s));
#endif

			}
				break;

			default: {
				unsigned int addr = (unsigned int) arg;
#ifdef _MSC_VER
				__asm push addr;
#else
				__asm__ __volatile__("pushl %0" : : "g"(addr));
#endif

			}
				break;
			}
		}
	}

#ifdef _MSC_VER
	__asm call func;
	__asm mov int ptr[retval], eax;
#else
	__asm__ __volatile__("call *%0" : : "g"(func));
	__asm__ __volatile__("movl %%eax, %0" : "=g"(retval));
#endif

	if (strcmp(returntype, "int") == 0) {
		g_string_append_printf(result, "%d", retval);
	} else if (strcmp(returntype, "double") == 0) {
#ifdef _MSC_VER
		__asm fstp [_real.real];
#else
		__asm__ __volatile__("fstp %0" : "=g"(_real.real));
#endif
		g_string_append_printf(result, "%lf", _real.real);
	} else if (strcmp(returntype, "string") == 0) {
		GString *ret = (GString *) retval;
		if (ret) {
			g_string_append(result, ret->str);
			g_string_free(ret, TRUE);
		}
	} else if (strcmp(returntype, "char") == 0) {
	} else {
	}

#ifdef _MSC_VER
	__asm mov esp, int ptr[_esp];
#else
	__asm__ __volatile__("movl %0, %%esp" : : "g"(_esp));
#endif

	if (!self) {
		FreeLibrary(hModule);
	}

	json_object_put(obj);

	return result;
}

void geticon() {
	SHFILEINFOA fi;
	CoInitialize(NULL );
	ZeroMemory(&fi, sizeof(SHFILEINFO));

	SHGetFileInfo("/cygdrive/c/", -1, &fi, sizeof(SHFILEINFOA), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES);
	GString *ret = icon_to_stream(fi.hIcon, "png");
	g_file_set_contents("e:/a.png", ret->str, ret->len, NULL );
	DestroyIcon(fi.hIcon);
	CoUninitialize();
}

GString *icon_to_stream(HICON hIcon, const char *type) {
	GString *ret = NULL;
	GdiplusStartupInput input;
	ULONG_PTR token;
	GpBitmap *bitmap = NULL;
	UINT num, size, i;
	ImageCodecInfo *encoders = NULL;

	ZeroMemory(&input, sizeof(GdiplusStartupInput));
	input.GdiplusVersion = 1;

	GdiplusStartup(&token, &input, NULL );

	GdipCreateBitmapFromHICON(hIcon, &bitmap);

	GetImageEncodersSize(&num, &size);
	encoders = malloc(num * size);
	GdipGetImageEncoders(num, size, encoders);
	for (i = 0; i < num; i++) {
		char *info = bstr_to_string(encoders[i].FormatDescription);
		if (g_strcasecmp(info, type) == 0) {
			IStream *stream = NULL;
			ULARGE_INTEGER size;
			LARGE_INTEGER origin;
			char *str = NULL;

			CreateStreamOnHGlobal(NULL, 0, &stream);
			GdipSaveImageToStream(bitmap, stream, &encoders[i].Clsid, NULL );
			stream->lpVtbl->Seek(stream, origin, STREAM_SEEK_CUR, &size);
			stream->lpVtbl->Seek(stream, origin, STREAM_SEEK_SET, NULL );

			str = malloc(size.QuadPart);
			memset(str, 0, size.QuadPart);
			stream->lpVtbl->Read(stream, str, size.QuadPart, NULL );
			ret = g_string_new_len(str, size.QuadPart);
			free(str);

			stream->lpVtbl->Release(stream);
		}
		free(info);
		if (ret) {
			break;
		}
	}
	free(encoders);

	GdipDisposeImage(bitmap);
	GdiplusShutdown(token);

	return ret;
}
